#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project ：V2 
@File    ：T3.py
@IDE     ：PyCharm 
@Author  ：郭星
@Date    ：2025/10/8 15:01 
'''
'''
生成算例的
'''

# !/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Project ：气体调度算法系统
@File    ：gas_scheduling_system.py
@Description：包含算例生成器和四种优化算法（GA、PSO、SA、TS），可选择不同算例执行
'''
import os
import json
import random
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from typing import List, Tuple, Dict, Optional
import argparse

# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "SimHei", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示问题


class GasSchedulingInstanceGenerator:
    """气体调度问题算例生成器"""

    @staticmethod
    def generate_instance(num_workpieces: int,
                          num_components: int,
                          num_inflation_eq: int,
                          num_analysis_eq_per_component: int,
                          min_inflation_time: int = 5,
                          max_inflation_time: int = 15,
                          min_analysis_time: int = 3,
                          max_analysis_time: int = 10,
                          save_path: Optional[str] = None) -> Dict:
        """
        生成气体调度问题算例

        参数:
            num_workpieces: 工件数量
            num_components: 每个工件的组分数
            num_inflation_eq: 充气设备数量
            num_analysis_eq_per_component: 每种组分的分析设备数量
            min_inflation_time: 最小充气时间
            max_inflation_time: 最大充气时间
            min_analysis_time: 最小分析时间
            max_analysis_time: 最大分析时间
            save_path: 保存路径，为None则不保存

        返回:
            包含问题参数的字典
        """
        # 随机生成加工时间
        inflation_time = np.random.randint(min_inflation_time, max_inflation_time + 1,
                                           size=num_workpieces)
        analysis_time = np.random.randint(min_analysis_time, max_analysis_time + 1,
                                          size=(num_workpieces, num_components))

        # 构建算例字典
        instance = {
            'problem_type': 'gas_scheduling',
            'parameters': {
                'num_workpieces': num_workpieces,
                'num_components': num_components,
                'num_inflation_eq': num_inflation_eq,
                'num_analysis_eq_per_component': num_analysis_eq_per_component
            },
            'processing_times': {
                'inflation_time': inflation_time.tolist(),
                'analysis_time': analysis_time.tolist()
            },
            'metadata': {
                'min_inflation_time': min_inflation_time,
                'max_inflation_time': max_inflation_time,
                'min_analysis_time': min_analysis_time,
                'max_analysis_time': max_analysis_time
            }
        }

        # 保存算例
        if save_path:
            # 确保目录存在
            os.makedirs(os.path.dirname(save_path), exist_ok=True)
            with open(save_path, 'w', encoding='utf-8') as f:
                json.dump(instance, f, ensure_ascii=False, indent=2)
            print(f"算例已保存至: {save_path}")

        return instance

    @staticmethod
    def generate_multiple_instances(base_dir: str = "instances2"):
        """生成多种规模的算例"""
        # 定义不同规模的问题参数
        instance_sizes = [
            # 小规模
            {'num_workpieces': 5, 'num_components': 2,
             'num_inflation_eq': 2, 'num_analysis_eq_per_component': 1},
            # 中等规模
            {'num_workpieces': 10, 'num_components': 4,
             'num_inflation_eq': 3, 'num_analysis_eq_per_component': 2},
            # 大规模
            {'num_workpieces': 20, 'num_components': 6,
             'num_inflation_eq': 5, 'num_analysis_eq_per_component': 3},
            # 超大规模
            {'num_workpieces': 50, 'num_components': 8,
             'num_inflation_eq': 8, 'num_analysis_eq_per_component': 4}
        ]

        # 为每种规模生成3个不同算例
        for i, size_params in enumerate(instance_sizes):
            size_name = f"size_{i + 1}"
            size_dir = os.path.join(base_dir, size_name)

            for j in range(3):
                instance_path = os.path.join(size_dir, f"instance_{j + 1}.json")
                GasSchedulingInstanceGenerator.generate_instance(
                    **size_params,
                    save_path=instance_path
                )

    @staticmethod
    def load_instance(file_path: str) -> Dict:
        """加载算例文件"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"算例文件不存在: {file_path}")

        with open(file_path, 'r', encoding='utf-8') as f:
            instance = json.load(f)

        # 验证算例格式
        required_keys = ['problem_type', 'parameters', 'processing_times']
        for key in required_keys:
            if key not in instance:
                raise ValueError(f"算例格式错误，缺少必要字段: {key}")

        return instance


class BaseSchedulingAlgorithm:
    """调度算法基类，包含公共方法和属性"""

    def __init__(self,
                 instance: Dict,
                 max_iter: int = 100,
                 name: str = "算法"):
        """初始化基础参数"""
        # 从算例中获取参数
        params = instance['parameters']
        processing_times = instance['processing_times']

        self.num_workpieces = params['num_workpieces']
        self.num_components = params['num_components']
        self.num_inflation_eq = params['num_inflation_eq']
        self.num_analysis_eq_per_component = params['num_analysis_eq_per_component']

        # 加工时间
        self.inflation_time = np.array(processing_times['inflation_time'])
        self.analysis_time = np.array(processing_times['analysis_time'])

        self.max_iter = max_iter
        self.name = name

        self.best_solution = None
        self.best_time = float('inf')
        self.iteration_best_times = []  # 记录每代最佳时间，用于绘制收敛曲线

    def initialize_solution(self) -> Dict:
        """初始化一个解（调度方案）"""
        # 充气阶段基因
        inflation_order = random.sample(range(self.num_workpieces), self.num_workpieces)
        inflation_eq = [random.randint(0, self.num_inflation_eq - 1) for _ in range(self.num_workpieces)]

        # 分析阶段基因
        analysis_order = random.sample(range(self.num_workpieces), self.num_workpieces)

        # 组分串联组合基因
        component_groups = []
        group_test_order = []
        analysis_eq = []  # 与group_test_order和组分严格同步
        for _ in range(self.num_workpieces):
            max_groups = random.randint(1, self.num_components)
            groups = []
            for i in range(self.num_components):
                if i < max_groups:
                    groups.append(i)
                else:
                    groups.append(random.randint(0, max_groups - 1))
            component_groups.append(groups)

            # 串联组测试顺序
            unique_groups = list(sorted(set(groups)))
            random.shuffle(unique_groups)
            group_test_order.append(unique_groups)

            # 生成分析设备选择（确保与组内组分数一致）
            eq_for_workpiece = []
            for group_id in unique_groups:
                components_in_group = [j for j, g in enumerate(groups) if g == group_id]
                eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                for _ in components_in_group]
                eq_for_workpiece.append(eq_for_group)
            analysis_eq.append(eq_for_workpiece)

        return {
            'inflation_order': inflation_order,
            'inflation_eq': inflation_eq,
            'analysis_order': analysis_order,
            'component_groups': component_groups,
            'group_test_order': group_test_order,
            'analysis_eq': analysis_eq
        }

    def calculate_completion_time(self, solution: Dict) -> float:
        """计算最大完成时间，作为优化目标"""
        # 计算充气阶段时间
        inflation_machine_time = [0] * self.num_inflation_eq
        for workpiece in solution['inflation_order']:
            eq = solution['inflation_eq'][workpiece]
            start_time = inflation_machine_time[eq]
            end_time = start_time + self.inflation_time[workpiece]
            inflation_machine_time[eq] = end_time
        inflation_completion_time = max(inflation_machine_time)

        # 计算分析阶段时间
        analysis_machine_time = [[0 for _ in range(self.num_analysis_eq_per_component)]
                                 for _ in range(self.num_components)]
        workpiece_inflation_end = {}
        temp_machine_time = [0] * self.num_inflation_eq
        for workpiece in solution['inflation_order']:
            eq = solution['inflation_eq'][workpiece]
            start_time = temp_machine_time[eq]
            end_time = start_time + self.inflation_time[workpiece]
            temp_machine_time[eq] = end_time
            workpiece_inflation_end[workpiece] = end_time

        # 按分析顺序处理工件
        for workpiece in solution['analysis_order']:
            if workpiece < 0 or workpiece >= self.num_workpieces:
                continue

            start_analysis_time = workpiece_inflation_end.get(workpiece, 0)
            group_test_order = solution['group_test_order'][workpiece]
            analysis_eq = solution['analysis_eq'][workpiece]

            # 遍历组测试顺序
            for group_idx, group_id in enumerate(group_test_order):
                if group_idx < 0 or group_idx >= len(analysis_eq):
                    continue

                # 获取该组实际包含的组分
                components_in_group = [j for j, g in enumerate(solution['component_groups'][workpiece])
                                       if g == group_id]
                # 获取该组的设备列表
                eq_for_group = analysis_eq[group_idx]

                # 确保设备列表长度与组分数一致
                if len(eq_for_group) != len(components_in_group):
                    eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in components_in_group]
                    analysis_eq[group_idx] = eq_for_group

                # 计算组准备时间（所有设备都准备好才能开始）
                group_ready_time = start_analysis_time
                for comp_idx, component in enumerate(components_in_group):
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue
                    eq = eq_for_group[comp_idx]
                    group_ready_time = max(group_ready_time, analysis_machine_time[component][eq])

                # 计算串联组的总时间（取最长的单个测试时间）
                max_analysis_time = max(
                    self.analysis_time[workpiece][component]
                    for comp_idx, component in enumerate(components_in_group)
                    if comp_idx < len(eq_for_group)
                ) if components_in_group else 0

                # 所有串联设备的结束时间相同
                group_end_time = group_ready_time + max_analysis_time

                # 更新所有串联设备的时间
                for comp_idx, component in enumerate(components_in_group):
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue
                    eq = eq_for_group[comp_idx]
                    analysis_machine_time[component][eq] = group_end_time

                # 更新组的完成时间，作为下一组的开始时间下限
                start_analysis_time = group_end_time

        analysis_completion_time = max(max(machine_times) for machine_times in analysis_machine_time)
        total_time = max(inflation_completion_time, analysis_completion_time)
        return total_time

    def get_schedule_details(self, solution: Dict) -> Tuple[Dict, Dict]:
        """获取调度详细信息，用于绘制甘特图"""
        # 充气阶段详细信息
        inflation_details = {}
        for eq in range(self.num_inflation_eq):
            inflation_details[eq] = []

        inflation_machine_time = [0] * self.num_inflation_eq
        for workpiece in solution['inflation_order']:
            eq = solution['inflation_eq'][workpiece]
            start_time = inflation_machine_time[eq]
            end_time = start_time + self.inflation_time[workpiece]
            inflation_machine_time[eq] = end_time
            inflation_details[eq].append({
                'workpiece': workpiece,
                'start': start_time,
                'end': end_time
            })

        # 分析阶段详细信息
        analysis_details = {}
        for component in range(self.num_components):
            analysis_details[component] = {}
            for eq in range(self.num_analysis_eq_per_component):
                analysis_details[component][eq] = []

        workpiece_inflation_end = {}
        temp_machine_time = [0] * self.num_inflation_eq
        for workpiece in solution['inflation_order']:
            eq = solution['inflation_eq'][workpiece]
            start_time = temp_machine_time[eq]
            end_time = start_time + self.inflation_time[workpiece]
            temp_machine_time[eq] = end_time
            workpiece_inflation_end[workpiece] = end_time

        analysis_machine_time = [[0 for _ in range(self.num_analysis_eq_per_component)]
                                 for _ in range(self.num_components)]

        for workpiece in solution['analysis_order']:
            if workpiece < 0 or workpiece >= self.num_workpieces:
                continue

            start_analysis_time = workpiece_inflation_end.get(workpiece, 0)
            group_test_order = solution['group_test_order'][workpiece]
            analysis_eq = solution['analysis_eq'][workpiece]

            for group_idx, group_id in enumerate(group_test_order):
                if group_idx < 0 or group_idx >= len(analysis_eq):
                    continue

                components_in_group = [j for j, g in enumerate(solution['component_groups'][workpiece])
                                       if g == group_id]
                eq_for_group = analysis_eq[group_idx]

                if len(eq_for_group) != len(components_in_group):
                    eq_for_group = [random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in components_in_group]

                # 计算组准备时间
                group_ready_time = start_analysis_time
                for comp_idx, component in enumerate(components_in_group):
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue
                    eq = eq_for_group[comp_idx]
                    group_ready_time = max(group_ready_time, analysis_machine_time[component][eq])

                # 计算串联组的总时间
                max_analysis_time = max(
                    self.analysis_time[workpiece][component]
                    for comp_idx, component in enumerate(components_in_group)
                    if comp_idx < len(eq_for_group)
                ) if components_in_group else 0

                # 所有串联设备的结束时间相同
                group_end_time = group_ready_time + max_analysis_time

                # 更新所有串联设备的时间并记录详细信息
                for comp_idx, component in enumerate(components_in_group):
                    if comp_idx < 0 or comp_idx >= len(eq_for_group):
                        continue
                    eq = eq_for_group[comp_idx]
                    analysis_machine_time[component][eq] = group_end_time
                    analysis_details[component][eq].append({
                        'workpiece': workpiece,
                        'start': group_ready_time,
                        'end': group_end_time,
                        'group': group_id,
                        'individual_time': self.analysis_time[workpiece][component]
                    })

                start_analysis_time = group_end_time

        return inflation_details, analysis_details

    def plot_gantt_chart(self, solution: Dict = None, title_suffix: str = ""):
        """绘制甘特图展示调度方案"""
        if solution is None:
            solution = self.best_solution

        if solution is None:
            print(f"{self.name} 没有可用的调度方案进行绘制")
            return

        inflation_details, analysis_details = self.get_schedule_details(solution)

        # 生成颜色列表，用于区分不同工件
        colors = plt.cm.tab10(np.linspace(0, 1, self.num_workpieces))

        # 创建画布
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8 + self.num_components * 1.5))
        fig.suptitle(f'{self.name} 气体调度甘特图 {title_suffix}', fontsize=16)

        # 绘制充气阶段甘特图
        y_ticks = []
        y_labels = []

        for i, eq in enumerate(inflation_details.keys()):
            y_pos = i + 1
            y_ticks.append(y_pos)
            y_labels.append(f'充气设备 {eq}')

            for task in inflation_details[eq]:
                workpiece = task['workpiece']
                start = task['start']
                end = task['end']
                ax1.barh(y_pos, end - start, left=start, height=0.6,
                         color=colors[workpiece], edgecolor='black')
                ax1.text(start + (end - start) / 2, y_pos, f'瓶{workpiece}',
                         ha='center', va='center', color='white', fontweight='bold')

        ax1.set_yticks(y_ticks)
        ax1.set_yticklabels(y_labels)
        ax1.set_xlabel('时间')
        ax1.set_title('充气阶段')
        ax1.grid(True, axis='x', linestyle='--', alpha=0.7)

        # 绘制分析阶段甘特图，突出显示串联关系
        y_ticks = []
        y_labels = []
        current_y = 1

        # 收集所有组信息用于标注串联关系
        all_groups = {}

        for component in sorted(analysis_details.keys()):
            for eq in sorted(analysis_details[component].keys()):
                y_pos = current_y
                y_ticks.append(y_pos)
                y_labels.append(f'组分{component}分析设备 {eq}')

                for task in analysis_details[component][eq]:
                    workpiece = task['workpiece']
                    start = task['start']
                    end = task['end']
                    group_id = task['group']
                    individual_time = task['individual_time']

                    # 存储组信息以便后续绘制连接线
                    group_key = (workpiece, group_id)
                    if group_key not in all_groups:
                        all_groups[group_key] = []
                    all_groups[group_key].append((y_pos, start, end))

                    # 绘制任务条
                    ax2.barh(y_pos, end - start, left=start, height=0.6,
                             color=colors[workpiece], edgecolor='black')
                    # 标注瓶号和单个设备的时间
                    label = f'瓶{workpiece}({individual_time})'
                    ax2.text(start + (end - start) / 2, y_pos, label,
                             ha='center', va='center', color='white', fontweight='bold', fontsize=8)

                current_y += 1

            # 不同组分之间添加间隔
            current_y += 0.5

        # 绘制串联组连接线
        for group_key, tasks in all_groups.items():
            if len(tasks) > 1:  # 只处理包含多个设备的串联组
                workpiece, group_id = group_key
                mid_time = (tasks[0][1] + tasks[0][2]) / 2
                y_positions = [task[0] for task in tasks]
                ax2.plot([mid_time, mid_time], [min(y_positions) - 0.3, max(y_positions) + 0.3],
                         'k--', linewidth=1.5, alpha=0.7)
                ax2.text(mid_time, max(y_positions) + 0.4, f'串联组{group_id}',
                         ha='center', va='bottom', color='black', fontsize=7, fontweight='bold')

        ax2.set_yticks(y_ticks)
        ax2.set_yticklabels(y_labels)
        ax2.set_xlabel('时间')
        ax2.set_title('分析阶段（虚线连接表示串联测试）')
        ax2.grid(True, axis='x', linestyle='--', alpha=0.7)

        # 添加图例
        legend_items = [mpatches.Patch(color=colors[i], label=f'气瓶 {i}')
                        for i in range(min(10, self.num_workpieces))]
        if self.num_workpieces > 10:
            legend_items.append(mpatches.Patch(color='white', label=f'... 共{self.num_workpieces}个'))
        fig.legend(handles=legend_items, loc='lower center', ncol=5, bbox_to_anchor=(0.5, -0.02))

        plt.tight_layout(rect=[0, 0.05, 1, 0.95])
        plt.show()

    def optimize(self):
        """优化接口，由子类实现"""
        raise NotImplementedError("子类必须实现优化方法")


class GeneticAlgorithm(BaseSchedulingAlgorithm):
    """遗传算法"""

    def __init__(self,
                 instance: Dict,
                 population_size: int = 50,
                 generations: int = 100,
                 crossover_rate: float = 0.8,
                 mutation_rate: float = 0.1,
                 name: str = "遗传算法(GA)"):
        super().__init__(instance, generations, name)
        self.population_size = population_size
        self.crossover_rate = crossover_rate
        self.mutation_rate = mutation_rate
        self.population = []

    def initialize_population(self):
        """初始化种群"""
        self.population = [self.initialize_solution() for _ in range(self.population_size)]

    def select_parents(self, fitness_values: List[float]) -> Tuple[Dict, Dict]:
        """轮盘赌选择父母"""
        total_fitness = sum(fitness_values)
        probabilities = [f / total_fitness for f in fitness_values]

        parent1_idx = np.random.choice(self.population_size, p=probabilities)
        parent2_idx = np.random.choice(self.population_size, p=probabilities)
        while parent1_idx == parent2_idx:
            parent2_idx = np.random.choice(self.population_size, p=probabilities)

        return self.population[parent1_idx], self.population[parent2_idx]

    def crossover(self, parent1: Dict, parent2: Dict) -> Tuple[Dict, Dict]:
        """交叉操作"""
        if random.random() > self.crossover_rate:
            return parent1.copy(), parent2.copy()

        # 顺序交叉（部分映射交叉）
        def order_crossover(order1, order2):
            size = len(order1)
            a, b = random.sample(range(size), 2)
            if a > b:
                a, b = b, a

            child1 = [-1] * size
            child2 = [-1] * size
            child1[a:b + 1] = order1[a:b + 1]
            child2[a:b + 1] = order2[a:b + 1]

            def fill_child(child, parent, start, end):
                ptr = (end + 1) % size
                for i in range(end + 1, end + 1 + size):
                    current = parent[i % size]
                    if current not in child:
                        child[ptr] = current
                        ptr = (ptr + 1) % size
                return child

            child1 = fill_child(child1, order2, a, b)
            child2 = fill_child(child2, order1, a, b)
            return child1, child2

        # 交叉充气顺序和分析顺序
        child1_inflation_order, child2_inflation_order = order_crossover(
            parent1['inflation_order'], parent2['inflation_order']
        )
        child1_analysis_order, child2_analysis_order = order_crossover(
            parent1['analysis_order'], parent2['analysis_order']
        )

        # 深拷贝确保子染色体独立
        child1 = {
            'inflation_order': child1_inflation_order,
            'inflation_eq': parent1['inflation_eq'].copy(),
            'analysis_order': child1_analysis_order,
            'component_groups': [g.copy() for g in parent1['component_groups']],
            'group_test_order': [g.copy() for g in parent1['group_test_order']],
            'analysis_eq': [g.copy() for g in parent1['analysis_eq']]
        }
        for i in range(len(child1['analysis_eq'])):
            child1['analysis_eq'][i] = [e.copy() for e in child1['analysis_eq'][i]]

        child2 = {
            'inflation_order': child2_inflation_order,
            'inflation_eq': parent2['inflation_eq'].copy(),
            'analysis_order': child2_analysis_order,
            'component_groups': [g.copy() for g in parent2['component_groups']],
            'group_test_order': [g.copy() for g in parent2['group_test_order']],
            'analysis_eq': [g.copy() for g in parent2['analysis_eq']]
        }
        for i in range(len(child2['analysis_eq'])):
            child2['analysis_eq'][i] = [e.copy() for e in child2['analysis_eq'][i]]

        # 充气设备选择交叉
        for i in range(self.num_workpieces):
            if random.random() < 0.5:
                child1['inflation_eq'][i] = parent2['inflation_eq'][i]
                child2['inflation_eq'][i] = parent1['inflation_eq'][i]

        # 组分分组交叉
        for i in range(self.num_workpieces):
            if random.random() < 0.5:
                # 交叉组分分组
                child1['component_groups'][i] = parent2['component_groups'][i].copy()
                child2['component_groups'][i] = parent1['component_groups'][i].copy()

                # 重新计算组测试顺序和设备列表
                # 对child1处理
                groups1 = child1['component_groups'][i]
                unique_groups1 = list(sorted(set(groups1)))
                random.shuffle(unique_groups1)
                child1['group_test_order'][i] = unique_groups1
                # 重新生成设备列表确保长度匹配
                new_eq1 = []
                for group_id in unique_groups1:
                    components = [j for j, g in enumerate(groups1) if g == group_id]
                    new_eq1.append([random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in components])
                child1['analysis_eq'][i] = new_eq1

                # 对child2处理
                groups2 = child2['component_groups'][i]
                unique_groups2 = list(sorted(set(groups2)))
                random.shuffle(unique_groups2)
                child2['group_test_order'][i] = unique_groups2
                # 重新生成设备列表确保长度匹配
                new_eq2 = []
                for group_id in unique_groups2:
                    components = [j for j, g in enumerate(groups2) if g == group_id]
                    new_eq2.append([random.randint(0, self.num_analysis_eq_per_component - 1)
                                    for _ in components])
                child2['analysis_eq'][i] = new_eq2

        return child1, child2

    def mutate(self, chromosome: Dict) -> Dict:
        """变异操作"""
        # 深拷贝染色体
        mutated = {
            'inflation_order': chromosome['inflation_order'].copy(),
            'inflation_eq': chromosome['inflation_eq'].copy(),
            'analysis_order': chromosome['analysis_order'].copy(),
            'component_groups': [g.copy() for g in chromosome['component_groups']],
            'group_test_order': [g.copy() for g in chromosome['group_test_order']],
            'analysis_eq': [g.copy() for g in chromosome['analysis_eq']]
        }
        for i in range(len(mutated['analysis_eq'])):
            mutated['analysis_eq'][i] = [e.copy() for e in mutated['analysis_eq'][i]]

        # 充气顺序变异
        if random.random() < self.mutation_rate:
            idx1, idx2 = random.sample(range(self.num_workpieces), 2)
            mutated['inflation_order'][idx1], mutated['inflation_order'][idx2] = \
                mutated['inflation_order'][idx2], mutated['inflation_order'][idx1]

        # 分析顺序变异
        if random.random() < self.mutation_rate:
            idx1, idx2 = random.sample(range(self.num_workpieces), 2)
            mutated['analysis_order'][idx1], mutated['analysis_order'][idx2] = \
                mutated['analysis_order'][idx2], mutated['analysis_order'][idx1]

        # 充气设备选择变异
        for i in range(self.num_workpieces):
            if random.random() < self.mutation_rate:
                mutated['inflation_eq'][i] = random.randint(0, self.num_inflation_eq - 1)

        # 组分分组变异
        for i in range(self.num_workpieces):
            if random.random() < self.mutation_rate:
                # 随机改变一个组分的组号
                comp_idx = random.randint(0, self.num_components - 1)
                current_groups = mutated['component_groups'][i]
                possible_groups = list(set(current_groups))

                if len(possible_groups) == 1:
                    new_group = 1 if possible_groups[0] == 0 else 0
                else:
                    new_group = random.choice([g for g in possible_groups if g != current_groups[comp_idx]])

                current_groups[comp_idx] = new_group

                # 更新组测试顺序和设备列表
                unique_groups = list(sorted(set(current_groups)))
                random.shuffle(unique_groups)
                mutated['group_test_order'][i] = unique_groups

                # 重新生成分析设备选择
                new_analysis_eq = []
                for group_id in unique_groups:
                    components_in_group = [j for j, g in enumerate(current_groups) if g == group_id]
                    new_analysis_eq.append([
                        random.randint(0, self.num_analysis_eq_per_component - 1)
                        for _ in components_in_group
                    ])
                mutated['analysis_eq'][i] = new_analysis_eq

        # 分析设备选择变异
        for i in range(self.num_workpieces):
            for group_idx in range(len(mutated['group_test_order'][i])):
                if random.random() < self.mutation_rate:
                    eq_list = mutated['analysis_eq'][i][group_idx]
                    if len(eq_list) > 0:
                        comp_idx = random.randint(0, len(eq_list) - 1)
                        eq_list[comp_idx] = random.randint(0, self.num_analysis_eq_per_component - 1)

        return mutated

    def optimize(self) -> Tuple[Dict, float]:
        """执行遗传算法优化过程"""
        self.initialize_population()
        self.iteration_best_times = []
        self.best_time = float('inf')
        self.best_solution = None

        for generation in range(self.max_iter):
            try:
                # 计算每个染色体的完成时间
                completion_times = [self.calculate_completion_time(chrom) for chrom in self.population]
                fitness_values = [1.0 / time for time in completion_times]
            except Exception as e:
                print(f"{self.name} 计算适应度时出错: {e}")
                self.initialize_population()
                completion_times = [self.calculate_completion_time(chrom) for chrom in self.population]
                fitness_values = [1.0 / time for time in completion_times]

            current_best_idx = np.argmin(completion_times)
            current_best_time = completion_times[current_best_idx]
            current_best_solution = self.population[current_best_idx]

            # 记录当前代最佳完成时间
            self.iteration_best_times.append(current_best_time)

            if current_best_time < self.best_time:
                self.best_time = current_best_time
                self.best_solution = current_best_solution

            if generation % 10 == 0:
                print(f"{self.name} 第 {generation} 代: 最佳完成时间 = {current_best_time:.2f}")

            # 生成新一代种群
            new_population = []
            # 精英保留
            elite_size = max(1, int(self.population_size * 0.1))
            elite_indices = np.argsort(completion_times)[:elite_size]
            for idx in elite_indices:
                new_population.append(self.population[idx])

            # 生成新个体
            while len(new_population) < self.population_size:
                parent1, parent2 = self.select_parents(fitness_values)
                child1, child2 = self.crossover(parent1, parent2)
                child1 = self.mutate(child1)
                child2 = self.mutate(child2)
                new_population.append(child1)
                if len(new_population) < self.population_size:
                    new_population.append(child2)

            self.population = new_population

        return self.best_solution, self.best_time


class ParticleSwarmOptimization(BaseSchedulingAlgorithm):
    """粒子群优化算法"""

    def __init__(self,
                 instance: Dict,
                 swarm_size: int = 50,
                 max_iter: int = 100,
                 w: float = 0.7,  # 惯性权重
                 c1: float = 1.5,  # 认知系数
                 c2: float = 1.5,  # 社会系数
                 name: str = "粒子群优化(PSO)"):
        super().__init__(instance, max_iter, name)
        self.swarm_size = swarm_size
        self.w = w  # 惯性权重
        self.c1 = c1  # 认知系数
        self.c2 = c2  # 社会系数

        self.swarm = []  # 粒子群
        self.pbest = []  # 个体最优解
        self.pbest_times = []  # 个体最优时间

    def initialize_swarm(self):
        """初始化粒子群"""
        self.swarm = [self.initialize_solution() for _ in range(self.swarm_size)]
        self.pbest = [s.copy() for s in self.swarm]
        self.pbest_times = [self.calculate_completion_time(s) for s in self.pbest]

        # 初始化全局最优
        best_idx = np.argmin(self.pbest_times)
        self.best_time = self.pbest_times[best_idx]
        self.best_solution = self.pbest[best_idx].copy()

    def update_particle(self, particle: Dict, pbest: Dict, gbest: Dict) -> Dict:
        """更新粒子位置"""
        new_particle = {
            'inflation_order': particle['inflation_order'].copy(),
            'inflation_eq': particle['inflation_eq'].copy(),
            'analysis_order': particle['analysis_order'].copy(),
            'component_groups': [g.copy() for g in particle['component_groups']],
            'group_test_order': [g.copy() for g in particle['group_test_order']],
            'analysis_eq': [g.copy() for g in particle['analysis_eq']]
        }
        for i in range(len(new_particle['analysis_eq'])):
            new_particle['analysis_eq'][i] = [e.copy() for e in new_particle['analysis_eq'][i]]

        # 向个体最优学习
        if random.random() < self.c1 / (self.c1 + self.c2):
            # 采纳个体最优的充气顺序
            if random.random() < 0.5:
                new_particle['inflation_order'] = pbest['inflation_order'].copy()
            # 采纳个体最优的分析顺序
            if random.random() < 0.5:
                new_particle['analysis_order'] = pbest['analysis_order'].copy()

        # 向全局最优学习
        if random.random() < self.c2 / (self.c1 + self.c2):
            # 采纳全局最优的充气设备分配
            for i in range(self.num_workpieces):
                if random.random() < 0.3:
                    new_particle['inflation_eq'][i] = gbest['inflation_eq'][i]

            # 采纳全局最优的组分分组
            for i in range(self.num_workpieces):
                if random.random() < 0.2:
                    new_particle['component_groups'][i] = gbest['component_groups'][i].copy()
                    # 更新对应的测试顺序和设备
                    groups = new_particle['component_groups'][i]
                    unique_groups = list(sorted(set(groups)))
                    random.shuffle(unique_groups)
                    new_particle['group_test_order'][i] = unique_groups

                    new_eq = []
                    for group_id in unique_groups:
                        components = [j for j, g in enumerate(groups) if g == group_id]
                        new_eq.append([random.randint(0, self.num_analysis_eq_per_component - 1)
                                       for _ in components])
                    new_particle['analysis_eq'][i] = new_eq

        # 随机变异（模拟惯性）
        if random.random() < self.w:
            # 随机交换充气顺序中的两个工件
            if random.random() < 0.4:
                idx1, idx2 = random.sample(range(self.num_workpieces), 2)
                new_particle['inflation_order'][idx1], new_particle['inflation_order'][idx2] = \
                    new_particle['inflation_order'][idx2], new_particle['inflation_order'][idx1]

            # 随机交换分析顺序中的两个工件
            if random.random() < 0.4:
                idx1, idx2 = random.sample(range(self.num_workpieces), 2)
                new_particle['analysis_order'][idx1], new_particle['analysis_order'][idx2] = \
                    new_particle['analysis_order'][idx2], new_particle['analysis_order'][idx1]

            # 随机改变一个充气设备
            if random.random() < 0.3:
                idx = random.randint(0, self.num_workpieces - 1)
                new_particle['inflation_eq'][idx] = random.randint(0, self.num_inflation_eq - 1)

        return new_particle

    def optimize(self) -> Tuple[Dict, float]:
        """执行粒子群优化"""
        self.initialize_swarm()
        self.iteration_best_times = []

        for iter in range(self.max_iter):
            for i in range(self.swarm_size):
                # 更新粒子
                new_particle = self.update_particle(
                    self.swarm[i], self.pbest[i], self.best_solution
                )
                new_time = self.calculate_completion_time(new_particle)

                # 更新个体最优
                if new_time < self.pbest_times[i]:
                    self.pbest[i] = new_particle.copy()
                    self.pbest_times[i] = new_time

                    # 更新全局最优
                    if new_time < self.best_time:
                        self.best_time = new_time
                        self.best_solution = new_particle.copy()

            # 记录当前迭代的最佳时间
            self.iteration_best_times.append(self.best_time)

            if iter % 10 == 0:
                print(f"{self.name} 第 {iter} 代: 最佳完成时间 = {self.best_time:.2f}")

            # 动态调整惯性权重
            self.w = self.w * 0.99

        return self.best_solution, self.best_time


class SimulatedAnnealing(BaseSchedulingAlgorithm):
    """模拟退火算法"""

    def __init__(self,
                 instance: Dict,
                 init_temp: float = 100.0,
                 cool_rate: float = 0.95,
                 max_iter: int = 100,
                 name: str = "模拟退火(SA)"):
        super().__init__(instance, max_iter, name)
        self.init_temp = init_temp  # 初始温度
        self.cool_rate = cool_rate  # 冷却速率
        self.current_solution = None
        self.current_time = float('inf')

    def generate_neighbor(self, solution: Dict) -> Dict:
        """生成邻域解"""
        new_solution = {
            'inflation_order': solution['inflation_order'].copy(),
            'inflation_eq': solution['inflation_eq'].copy(),
            'analysis_order': solution['analysis_order'].copy(),
            'component_groups': [g.copy() for g in solution['component_groups']],
            'group_test_order': [g.copy() for g in solution['group_test_order']],
            'analysis_eq': [g.copy() for g in solution['analysis_eq']]
        }
        for i in range(len(new_solution['analysis_eq'])):
            new_solution['analysis_eq'][i] = [e.copy() for e in new_solution['analysis_eq'][i]]

        # 随机选择一种变异方式
        mutation_type = random.choice(['order', 'equipment', 'group'])

        if mutation_type == 'order':
            # 交换顺序中的两个元素
            if random.random() < 0.5:
                # 交换充气顺序
                idx1, idx2 = random.sample(range(self.num_workpieces), 2)
                new_solution['inflation_order'][idx1], new_solution['inflation_order'][idx2] = \
                    new_solution['inflation_order'][idx2], new_solution['inflation_order'][idx1]
            else:
                # 交换分析顺序
                idx1, idx2 = random.sample(range(self.num_workpieces), 2)
                new_solution['analysis_order'][idx1], new_solution['analysis_order'][idx2] = \
                    new_solution['analysis_order'][idx2], new_solution['analysis_order'][idx1]

        elif mutation_type == 'equipment':
            # 改变设备分配
            if random.random() < 0.5:
                # 改变充气设备
                idx = random.randint(0, self.num_workpieces - 1)
                new_solution['inflation_eq'][idx] = random.randint(0, self.num_inflation_eq - 1)
            else:
                # 改变分析设备
                wp_idx = random.randint(0, self.num_workpieces - 1)
                if new_solution['group_test_order'][wp_idx]:
                    group_idx = random.choice(range(len(new_solution['group_test_order'][wp_idx])))
                    eq_list = new_solution['analysis_eq'][wp_idx][group_idx]
                    if eq_list:
                        comp_idx = random.randint(0, len(eq_list) - 1)
                        eq_list[comp_idx] = random.randint(0, self.num_analysis_eq_per_component - 1)

        elif mutation_type == 'group':
            # 改变组分分组
            wp_idx = random.randint(0, self.num_workpieces - 1)
            comp_idx = random.randint(0, self.num_components - 1)
            current_groups = new_solution['component_groups'][wp_idx]
            possible_groups = list(set(current_groups))

            if len(possible_groups) == 1:
                new_group = 1 if possible_groups[0] == 0 else 0
            else:
                new_group = random.choice([g for g in possible_groups if g != current_groups[comp_idx]])

            current_groups[comp_idx] = new_group

            # 更新组测试顺序和设备列表
            unique_groups = list(sorted(set(current_groups)))
            random.shuffle(unique_groups)
            new_solution['group_test_order'][wp_idx] = unique_groups

            # 重新生成分析设备选择
            new_analysis_eq = []
            for group_id in unique_groups:
                components_in_group = [j for j, g in enumerate(current_groups) if g == group_id]
                new_analysis_eq.append([
                    random.randint(0, self.num_analysis_eq_per_component - 1)
                    for _ in components_in_group
                ])
            new_solution['analysis_eq'][wp_idx] = new_analysis_eq

        return new_solution

    def optimize(self) -> Tuple[Dict, float]:
        """执行模拟退火优化"""
        # 初始化当前解和最优解
        self.current_solution = self.initialize_solution()
        self.current_time = self.calculate_completion_time(self.current_solution)
        self.best_solution = self.current_solution.copy()
        self.best_time = self.current_time
        self.iteration_best_times = []

        temp = self.init_temp  # 初始温度

        for iter in range(self.max_iter):
            # 生成邻域解
            neighbor = self.generate_neighbor(self.current_solution)
            neighbor_time = self.calculate_completion_time(neighbor)

            # 计算能量差（时间差）
            delta = self.current_time - neighbor_time  # 负数表示邻域解更差

            # 接受准则
            if delta > 0:  # 邻域解更好，接受
                self.current_solution = neighbor
                self.current_time = neighbor_time
            else:  # 邻域解更差，以一定概率接受
                prob = np.exp(delta / temp)
                if random.random() < prob:
                    self.current_solution = neighbor
                    self.current_time = neighbor_time

            # 更新全局最优
            if self.current_time < self.best_time:
                self.best_time = self.current_time
                self.best_solution = self.current_solution.copy()

            # 记录当前迭代的最佳时间
            self.iteration_best_times.append(self.best_time)

            # 降温
            temp *= self.cool_rate

            if iter % 10 == 0:
                print(f"{self.name} 第 {iter} 代: 最佳完成时间 = {self.best_time:.2f}")

        return self.best_solution, self.best_time


class TabuSearch(BaseSchedulingAlgorithm):
    """禁忌搜索算法"""

    def __init__(self,
                 instance: Dict,
                 tabu_size: int = 20,
                 neighborhood_size: int = 10,
                 max_iter: int = 100,
                 name: str = "禁忌搜索(TS)"):
        super().__init__(instance, max_iter, name)
        self.tabu_size = tabu_size  # 禁忌列表大小
        self.neighborhood_size = neighborhood_size  # 邻域解数量
        self.tabu_list = []  # 禁忌列表
        self.current_solution = None
        self.current_time = float('inf')

    def solution_hash(self, solution: Dict) -> str:
        """生成解的哈希值，用于禁忌列表"""
        return hash(tuple(solution['inflation_order']) + tuple(solution['inflation_eq']) +
                    tuple(solution['analysis_order']))

    def generate_neighborhood(self, solution: Dict) -> List[Dict]:
        """生成邻域解"""
        neighborhood = []
        for _ in range(self.neighborhood_size):
            new_solution = {
                'inflation_order': solution['inflation_order'].copy(),
                'inflation_eq': solution['inflation_eq'].copy(),
                'analysis_order': solution['analysis_order'].copy(),
                'component_groups': [g.copy() for g in solution['component_groups']],
                'group_test_order': [g.copy() for g in solution['group_test_order']],
                'analysis_eq': [g.copy() for g in solution['analysis_eq']]
            }
            for i in range(len(new_solution['analysis_eq'])):
                new_solution['analysis_eq'][i] = [e.copy() for e in new_solution['analysis_eq'][i]]

            # 随机选择变异方式
            mutation_type = random.choice(['order', 'equipment', 'group'])

            if mutation_type == 'order':
                if random.random() < 0.5:
                    idx1, idx2 = random.sample(range(self.num_workpieces), 2)
                    new_solution['inflation_order'][idx1], new_solution['inflation_order'][idx2] = \
                        new_solution['inflation_order'][idx2], new_solution['inflation_order'][idx1]
                else:
                    idx1, idx2 = random.sample(range(self.num_workpieces), 2)
                    new_solution['analysis_order'][idx1], new_solution['analysis_order'][idx2] = \
                        new_solution['analysis_order'][idx2], new_solution['analysis_order'][idx1]

            elif mutation_type == 'equipment':
                if random.random() < 0.5:
                    idx = random.randint(0, self.num_workpieces - 1)
                    new_solution['inflation_eq'][idx] = random.randint(0, self.num_inflation_eq - 1)
                else:
                    wp_idx = random.randint(0, self.num_workpieces - 1)
                    if new_solution['group_test_order'][wp_idx]:
                        group_idx = random.choice(range(len(new_solution['group_test_order'][wp_idx])))
                        eq_list = new_solution['analysis_eq'][wp_idx][group_idx]
                        if eq_list:
                            comp_idx = random.randint(0, len(eq_list) - 1)
                            eq_list[comp_idx] = random.randint(0, self.num_analysis_eq_per_component - 1)

            elif mutation_type == 'group':
                wp_idx = random.randint(0, self.num_workpieces - 1)
                comp_idx = random.randint(0, self.num_components - 1)
                current_groups = new_solution['component_groups'][wp_idx]
                possible_groups = list(set(current_groups))

                if len(possible_groups) == 1:
                    new_group = 1 if possible_groups[0] == 0 else 0
                else:
                    new_group = random.choice([g for g in possible_groups if g != current_groups[comp_idx]])

                current_groups[comp_idx] = new_group

                # 更新组测试顺序和设备列表
                unique_groups = list(sorted(set(current_groups)))
                random.shuffle(unique_groups)
                new_solution['group_test_order'][wp_idx] = unique_groups

                # 重新生成分析设备选择
                new_analysis_eq = []
                for group_id in unique_groups:
                    components_in_group = [j for j, g in enumerate(current_groups) if g == group_id]
                    new_analysis_eq.append([
                        random.randint(0, self.num_analysis_eq_per_component - 1)
                        for _ in components_in_group
                    ])
                new_solution['analysis_eq'][wp_idx] = new_analysis_eq

            neighborhood.append(new_solution)

        return neighborhood

    def optimize(self) -> Tuple[Dict, float]:
        """执行禁忌搜索优化"""
        # 初始化当前解和最优解
        self.current_solution = self.initialize_solution()
        self.current_time = self.calculate_completion_time(self.current_solution)
        self.best_solution = self.current_solution.copy()
        self.best_time = self.current_time
        self.iteration_best_times = []
        self.tabu_list = []

        for iter in range(self.max_iter):
            # 生成邻域解
            neighborhood = self.generate_neighborhood(self.current_solution)
            # 计算邻域解的目标函数值
            neighborhood_with_time = [(n, self.calculate_completion_time(n)) for n in neighborhood]
            # 按目标函数值排序（升序）
            neighborhood_with_time.sort(key=lambda x: x[1])

            # 选择下一个解
            next_solution = None
            next_time = float('inf')

            for sol, time in neighborhood_with_time:
                sol_hash = self.solution_hash(sol)
                # 特赦准则：如果解优于当前最优，则无视禁忌
                if time < self.best_time:
                    next_solution = sol
                    next_time = time
                    break
                # 非禁忌解
                if sol_hash not in self.tabu_list:
                    next_solution = sol
                    next_time = time
                    break

            # 如果所有解都在禁忌列表中，选择最好的一个
            if next_solution is None:
                next_solution, next_time = neighborhood_with_time[0]

            # 更新当前解
            self.current_solution = next_solution
            self.current_time = next_time

            # 更新禁忌列表
            self.tabu_list.append(self.solution_hash(next_solution))
            if len(self.tabu_list) > self.tabu_size:
                self.tabu_list.pop(0)  # 移除最早的禁忌解

            # 更新全局最优
            if self.current_time < self.best_time:
                self.best_time = self.current_time
                self.best_solution = self.current_solution.copy()

            # 记录当前迭代的最佳时间
            self.iteration_best_times.append(self.best_time)

            if iter % 10 == 0:
                print(f"{self.name} 第 {iter} 代: 最佳完成时间 = {self.best_time:.2f}")

        return self.best_solution, self.best_time


def plot_convergence_comparison(algorithms: List[BaseSchedulingAlgorithm]):
    """绘制多个算法的收敛曲线对比图"""
    plt.figure(figsize=(12, 8))

    # 使用不同的线型和标记区分不同算法
    styles = [
        {'color': 'blue', 'linestyle': '-', 'marker': 'o', 'label': algorithms[0].name},
        {'color': 'green', 'linestyle': '--', 'marker': 's', 'label': algorithms[1].name},
        {'color': 'red', 'linestyle': '-.', 'marker': '^', 'label': algorithms[2].name},
        {'color': 'purple', 'linestyle': ':', 'marker': 'd', 'label': algorithms[3].name}
    ]

    for i, algorithm in enumerate(algorithms):
        if i < len(styles):
            style = styles[i]
            plt.plot(range(1, len(algorithm.iteration_best_times) + 1),
                     algorithm.iteration_best_times,
                     color=style['color'],
                     linestyle=style['linestyle'],
                     marker=style['marker'],
                     markersize=4,
                     linewidth=2,
                     label=style['label'])

    plt.xlabel('迭代次数')
    plt.ylabel('最大完成时间')
    plt.title('不同算法收敛曲线对比+优劣种群在TS')
    plt.grid(True, linestyle='--', alpha=0.7)
    plt.legend()
    plt.tight_layout()
    plt.show()


def list_available_instances(base_dir: str = "instances2") -> List[str]:
    """列出所有可用的算例"""
    instances = []
    if not os.path.exists(base_dir):
        return instances

    for root, _, files in os.walk(base_dir):
        for file in files:
            if file.endswith(".json"):
                instances.append(os.path.join(root, file))
    return instances


def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='气体调度算法系统')
    parser.add_argument('--generate', action='store_true', help='生成默认算例集')
    parser.add_argument('--instance', type=str, help='指定算例文件路径')
    parser.add_argument('--algorithm', type=str, choices=['all', 'ga', 'pso', 'sa', 'ts'],
                        default='all', help='选择算法')
    parser.add_argument('--max-iter', type=int, default=100, help='最大迭代次数')

    args = parser.parse_args()

    # 生成算例
    if args.generate:
        print("生成默认算例集...")
        GasSchedulingInstanceGenerator.generate_multiple_instances()
        print("算例生成完成")
        return

    # 如果没有指定算例，列出可用算例并让用户选择
    if not args.instance:
        instances = list_available_instances()
        if not instances:
            print("没有找到可用算例，请先使用 --generate 生成算例")
            return

        print("可用算例:")
        for i, instance_path in enumerate(instances):
            print(f"{i + 1}. {instance_path}")

        try:
            choice = int(input("请选择算例 (输入序号): ")) - 1
            if 0 <= choice < len(instances):
                args.instance = instances[choice]
            else:
                print("无效选择")
                return
        except ValueError:
            print("无效输入")
            return

    # 加载算例
    try:
        print(f"加载算例: {args.instance}")
        instance = GasSchedulingInstanceGenerator.load_instance(args.instance)
        print(f"算例信息: {instance['parameters']}")
    except Exception as e:
        print(f"加载算例失败: {e}")
        return

    # 初始化算法
    algorithms = []
    if args.algorithm in ['all', 'ga']:
        ga = GeneticAlgorithm(
            instance=instance,
            population_size=50,
            generations=args.max_iter,
            crossover_rate=0.8,
            mutation_rate=0.1,
            name="遗传算法(GA)"
        )
        algorithms.append(ga)

    if args.algorithm in ['all', 'pso']:
        pso = ParticleSwarmOptimization(
            instance=instance,
            swarm_size=50,
            max_iter=args.max_iter,
            w=0.7,
            c1=1.5,
            c2=1.5,
            name="粒子群优化(PSO)"
        )
        algorithms.append(pso)

    if args.algorithm in ['all', 'sa']:
        sa = SimulatedAnnealing(
            instance=instance,
            init_temp=100.0,
            cool_rate=0.95,
            max_iter=args.max_iter,
            name="模拟退火(SA)"
        )
        algorithms.append(sa)

    if args.algorithm in ['all', 'ts']:
        ts = TabuSearch(
            instance=instance,
            tabu_size=20,
            neighborhood_size=10,
            max_iter=args.max_iter,
            name="禁忌搜索(TS)"
        )
        algorithms.append(ts)

    # 运行算法
    for algo in algorithms:
        print(f"\n开始运行 {algo.name}...")
        best_solution, best_time = algo.optimize()
        print(f"{algo.name} 最佳完成时间: {best_time:.2f}")

    # 绘制收敛曲线对比图（如果运行了多个算法）
    if len(algorithms) > 1:
        plot_convergence_comparison(algorithms)

    # 分别绘制每个算法的最佳结果甘特图
    for algo in algorithms:
        algo.plot_gantt_chart(title_suffix=f"(最佳完成时间: {algo.best_time:.2f})")


if __name__ == "__main__":
    main()
